/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Eclipse Public License (EPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/eclipse-1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */

import java.lang.reflect.Method;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.RVMMethod;

/**
 * A test to detect regressions in the quality of the optimizing compiler's
 * inline allocation sequence that is generated by inlining
 * {@link org.jikesrvm.runtime.VM_Entrypoints#resolvedNewScalarMethod} and
 * {@link org.jikesrvm.runtime.VM_Entrypoints#resolvedNewArrayMethod}.
 *
 * This test will only run on Jikes RVM as it reaches into the
 * internals of the VM to get access to the generated machine code for
 * the prototypical methods.
 *
 * It can also be used with org.jikesrvm.tools.oth.OptTestHarness to manually inspect and
 * tune the inline allocation sequence.
 */
class InlineAllocation {
  int a;
  int b;

  // We allow some pork for assertion checking in the inline allocation sequence,
  // However, we can't allow this to get out of hand...otherwise builds take way too long.
  static int assertionSpace = VM.VerifyAssertions ? (VM.BuildForIA32 ? 40 : 20) : 0;

  // Limits on sizes of allocation sequences.
  // Make them specific to the allocator so we can make them reasonably tight.
  static int alloc1Limit = assertionSpace + (VM.BuildForIA32 ? 100 : 30); // small object
  static int alloc2Limit = alloc1Limit + (VM.BuildForIA32 ? 8 : 2); // small array.  Should be only the store of the length different than small object
  static int alloc3Limit = assertionSpace + (VM.BuildForIA32 ? 100 : 30); // large object
  static int alloc4Limit = (VM.BuildForIA32 ? 40 : 11); // unknown size object. Should not be inlined at all.

  /**
   * A trivial method that should require the full prologue/epilogue
   * sequence (except that it won't use any nonvolatile registers...sigh).
   * This enables us to subtract off the expected prologue/epilogue size
   * from the machine code size of the other methods.
   */
  int trivial(InlineAllocation x) {
    return x.a;
  }


  /**
   * Allocation a scalar
   */
  InlineAllocation alloc1() {
    return new InlineAllocation();
  }

  /**
   * Allocate an array of small, constant size
   */
  int[] alloc2() {
    return new int[10];
  }

  /**
   * Allocate an array of large, constant size
   */
  int[] alloc3() {
    return new int[100000];
  }

  /**
   * Allocate an array of unknown size
   */
  int [] alloc4(int size) {
    return new int[size];
  }

  /**
   * Force compilation of each of the methods and report on the size
   * of the generated machine code.
   */
  public static void main(String[] args) throws Exception {
    Class<?> clazz = Class.forName("InlineAllocation");
    Method trivialJ = clazz.getDeclaredMethod("trivial", new Class[] {clazz});
    Method alloc1J = clazz.getDeclaredMethod("alloc1", (Class[])null);
    Method alloc2J = clazz.getDeclaredMethod("alloc2", (Class[])null);
    Method alloc3J = clazz.getDeclaredMethod("alloc3", (Class[])null);
    Method alloc4J = clazz.getDeclaredMethod("alloc4", new Class[] {Integer.TYPE});

    RVMMethod trivial = java.lang.reflect.JikesRVMSupport.getMethodOf(trivialJ);
    RVMMethod alloc1 = java.lang.reflect.JikesRVMSupport.getMethodOf(alloc1J);
    RVMMethod alloc2 = java.lang.reflect.JikesRVMSupport.getMethodOf(alloc2J);
    RVMMethod alloc3 = java.lang.reflect.JikesRVMSupport.getMethodOf(alloc3J);
    RVMMethod alloc4 = java.lang.reflect.JikesRVMSupport.getMethodOf(alloc4J);

    trivial.compile();
    int trivialSize = trivial.getCurrentCompiledMethod().numberOfInstructions();
    alloc1.compile();
    int alloc1Size = alloc1.getCurrentCompiledMethod().numberOfInstructions();
    alloc2.compile();
    int alloc2Size = alloc2.getCurrentCompiledMethod().numberOfInstructions();
    alloc3.compile();
    int alloc3Size = alloc3.getCurrentCompiledMethod().numberOfInstructions();
    alloc4.compile();
    int alloc4Size = alloc4.getCurrentCompiledMethod().numberOfInstructions();

    // System.out.println("Trivial method is "+trivialSize);
    // System.out.println("Scalar allocation size is "+alloc1Size);
    // System.out.println("Small array allocation is "+alloc2Size);
    // System.out.println("Large array allocation is "+alloc3Size);
    // System.out.println("Unknown size array allocation is "+alloc4Size);

    // Subtract off prologue/epilogue size. This is approximate!!!
    // If you really care, count the instructions by hand!
    alloc1Size -= trivialSize;
    alloc2Size -= trivialSize;
    alloc3Size -= trivialSize;
    alloc4Size -= trivialSize;

    System.out.println("Approximate scalar allocation size is "+alloc1Size);
    System.out.println("Approximate small array allocation is "+alloc2Size);
    System.out.println("Approximate large array allocation is "+alloc3Size);
    System.out.println("Approximate unknown size array allocation is "+alloc4Size);

    boolean fail = false;
    if (alloc1Size > alloc1Limit) {
      System.out.println("FAIL: scalar allocation is too porky");
      fail = true;
    }
    if (alloc2Size > alloc2Limit) {
      System.out.println("FAIL: small array allocation is too porky");
      fail = true;
    }
    if (alloc3Size > alloc3Limit) {
      System.out.println("FAIL: large array allocation is too porky");
      fail = true;
    }
    if (alloc4Size > alloc4Limit) {
      System.out.println("FAIL: unknown size array allocation is too porky");
      fail = true;
    }

    if (!fail) {
      System.out.println("Overall: SUCCESS");
    }
  }

}
